package com.grm.workflow.service.impl;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.grm.common.entity.PageResult;
import com.grm.common.exception.BusinessException;
import com.grm.common.utils.DateUtils;
import com.grm.common.utils.json.JsonUtils;
import com.grm.workflow.model.WfCategory;
import com.grm.workflow.entity.WfMetaInfo;
import com.grm.workflow.request.model.WfModelAddParam;
import com.grm.workflow.request.model.WfModelDesignParam;
import com.grm.workflow.request.model.WfModelQueryParam;
import com.grm.workflow.request.model.WfModelUpdateParam;
import com.grm.workflow.response.WfModelResponse;
import com.grm.workflow.service.WfCategoryService;
import com.grm.workflow.service.WfDeployFormService;
import com.grm.workflow.service.WfModelService;
import com.grm.workflow.utils.BpmnModelUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

@Service
@Slf4j
public class WfModelServiceImpl implements WfModelService {
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private WfDeployFormService wfDeployFormService;
    @Autowired
    private WfCategoryService wfCategoryService;

    @Override
    public PageResult<WfModelResponse> querySelective(Integer pageNum, Integer pageSize, WfModelQueryParam queryParam) {
        /*
        ModelQuery 主要查询的是与模型相关的数据库表
        ACT_RE_MODEL：这是 Flowable 中存储模型（如流程定义、表单等）信息的主要表。它包含了模型的基本信息，例如模型的 ID、名称、描述、创建时间、修改时间等。
        ACT_RE_DEPLOYMENT（可能会被查询）：这张表主要用于存储部署的信息，如部署 ID、部署时间等。
        ACT_HI_PROCINST、ACT_HI_TASKINST、ACT_HI_VARINST 等（通常与历史数据相关）：这些表用于存储流程实例、任务实例和变量实例等历史数据。
         */
        ModelQuery modelQuery = repositoryService.createModelQuery().latestVersion().orderByCreateTime().desc();
        // 构建查询条件
        if (StringUtils.isNotBlank(queryParam.getKey())) {
            modelQuery.modelKey(queryParam.getKey());
        }
        if (StringUtils.isNotBlank(queryParam.getName())) {
            modelQuery.modelNameLike("%" + queryParam.getName() + "%");
        }
        if (StringUtils.isNotBlank(queryParam.getCategory())) {
            modelQuery.modelCategory(queryParam.getCategory());
        }
        // 执行查询
        long total = modelQuery.count();
        if (total <= 0) {
            return new PageResult<>(List.of(), 0L);
        }
        /*
        SELECT m.*
        FROM ACT_RE_MODEL m
        INNER JOIN (
            -- 获取每个模型的最新版本, inner join只返回两个表中都有的数据
            SELECT KEY_ as model_key, MAX(VERSION_) AS latest_version
            FROM ACT_RE_MODEL
            GROUP BY KEY_
        ) latest
        ON m.KEY_ = latest.model_key AND m.VERSION_ = latest.latest_version
        ORDER BY m.CREATE_TIME_ DESC
        LIMIT 0, 10;


        ID_: 模型的唯一标识符（ID）。这是主键字段，用于唯一标识每个模型记录。
        REV_: 模型的修订版本号。假设一个模型的开发过程中有多人合作编辑，如果有多个修订，REV_ 可以帮助系统识别每次修改的顺序，并解决可能的并发冲突。
        NAME_: 模型的名称。存储模型的名称，便于用户识别和管理。
        KEY_: 模型的关键字。通常用于唯一标识模型或进行分组检索。
        CATEGORY_: 模型的分类。用于将模型分配到特定的类别，以便于组织和管理。
        CREATE_TIME_: 模型创建时间。记录模型首次创建的时间。
        LAST_UPDATE_TIME_: 模型最后更新时间。记录模型最后一次被更新的时间。
        VERSION_: 模型的版本号。当模型经过测试和审查后发布为一个新版本时，VERSION_ 用于标识这个新的版本
        META_INFO_: 模型的元信息。包含额外的描述信息或元数据，用于扩展模型的功能或提供更多背景信息。
        DEPLOYMENT_ID_: 部署 ID。将模型与特定的部署关联。如果模型已经被部署，该字段记录其所属的部署标识。
        EDITOR_SOURCE_VALUE_ID_: 编辑器源数据的 ID。与模型编辑器使用的源数据相关联。
        EDITOR_SOURCE_EXTRA_VALUE_ID_: 编辑器额外源数据的 ID。用于存储模型编辑器所需的额外源数据。
        TENANT_ID_: 租户 ID。在多租户环境中用于模型的隔离和管理，标识模型所属的租户。空字符串通常表示默认租户。
         */
        List<Model> modelList = modelQuery.listPage((pageNum - 1) * pageSize, pageSize);
        List<WfModelResponse> wfModelList = new ArrayList<>(modelList.size());
        modelList.forEach(model -> {
            WfModelResponse wfModelResponse = new WfModelResponse();
            wfModelResponse.setId(model.getId());
            wfModelResponse.setName(model.getName());
            wfModelResponse.setKey(model.getKey());
            wfModelResponse.setCategory(model.getCategory());
            // 获取中文
            WfCategory wfCategory = wfCategoryService.queryByCode(model.getCategory());
            wfModelResponse.setCategoryCn(wfCategory.getName());
            wfModelResponse.setVersion(model.getVersion());
            WfMetaInfo metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfo.class);
            if (metaInfo != null) {
                wfModelResponse.setRemark(metaInfo.getRemark());
                wfModelResponse.setFormId(metaInfo.getFormId());
            }
            wfModelList.add(wfModelResponse);
        });
        return new PageResult<>(wfModelList, total);
    }

    @Override
    public void add(WfModelAddParam addParam) {
        Model model = repositoryService.newModel();
        model.setName(addParam.getName());
        // 自动根据时间戳生成流程key
        model.setKey(String.format("Process_%s", DateUtils.getCurrentLocalDateTimeStr()));
        model.setCategory(addParam.getCategory());
        // 把备注添加到metaInfo中，后续在选择流程表单的时候需要把表单ID也设置到wfMetaInfo中
        if (StringUtils.isNotBlank(addParam.getRemark())) {
            WfMetaInfo wfMetaInfo = new WfMetaInfo();
            wfMetaInfo.setRemark(addParam.getRemark());
            model.setMetaInfo(JsonUtils.toJsonString(wfMetaInfo));
        }
        // 保存流程模型
        // 此时模型表ACT_RE_MODEL的版本VERSION_自动设置为1，并设置了创建时间CREATE_TIME_和最后更新时间LAST_UPDATE_TIME_
        repositoryService.saveModel(model);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void design(WfModelDesignParam designParam) {
        // 查询模型信息
        Model model = repositoryService.getModel(designParam.getModelId());
        if (ObjectUtil.isNull(model)) {
            throw new BusinessException("流程模型不存在！");
        }
        BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(designParam.getXml());
        if (ObjectUtil.isEmpty(bpmnModel)) {
            throw new BusinessException("获取模型设计失败！");
        }
        // 获取开始节点
        StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel);
        if (ObjectUtil.isNull(startEvent)) {
            throw new BusinessException("开始节点不存在，请检查流程设计是否有误！");
        }
        // 获取开始节点配置的表单Key
        if (StringUtils.isBlank(startEvent.getFormKey())) {
            throw new BusinessException("请配置流程表单");
        }
        String processName = bpmnModel.getMainProcess().getName();
        model.setName(processName);
        // 把formId添加到metaInfo中
        WfMetaInfo metaInfo = JsonUtils.parseObject(model.getMetaInfo(), WfMetaInfo.class);
        if (metaInfo != null) {
            WfMetaInfo wfMetaInfo = new WfMetaInfo();
            if (StringUtils.isNotBlank(metaInfo.getRemark())) {
                wfMetaInfo.setRemark(metaInfo.getRemark());
            }
            // 设置表单ID
            wfMetaInfo.setFormId(Long.parseLong(startEvent.getFormKey()));
            model.setMetaInfo(JsonUtils.toJsonString(wfMetaInfo));
        }

        // 保存流程模型
        repositoryService.saveModel(model);
        // 保存 BPMN XML
        byte[] bpmnXmlBytes = StringUtils.getBytes(designParam.getXml(), StandardCharsets.UTF_8);
        /*
            ACT_GE_BYTEARRAY 这个表用于存储所有的二进制数据（如模型源数据、流程图等）。

            INSERT INTO ACT_GE_BYTEARRAY (ID_, NAME_, BYTEARRAY_ID_, DEPLOYMENT_ID_, REV_)
            VALUES (UUID(), 'modelEditorSource', UUID(), NULL, 1);

            在数据插入后，ACT_RE_MODEL 表的 EDITOR_SOURCE_VALUE_ID_ 字段会被更新为新插入的 ACT_GE_BYTEARRAY 表中的 ID。
            假设 newModel.getId() 是模型的 ID，newEditorSourceId 是新插入的 ACT_GE_BYTEARRAY 表中的 ID：

            UPDATE ACT_RE_MODEL
            SET EDITOR_SOURCE_VALUE_ID_ = 'newEditorSourceId'
            WHERE ID_ = 'modelId';

            更新 ACT_RE_MODEL 表中的模型记录，以关联新存储的源数据
         */
        repositoryService.addModelEditorSource(model.getId(), bpmnXmlBytes);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String modelId) {
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtil.isNull(model)) {
            throw new RuntimeException("流程模型不存在！");
        }
        /*
        1. 删除模型本身的记录
        Flowable 的模型存储在 ACT_RE_MODEL 表中。删除模型时，会执行一个 SQL 语句来删除 ACT_RE_MODEL 表中的对应记录：
        DELETE FROM ACT_RE_MODEL WHERE ID_ = :modelId

        2. 删除模型的关联数据
        模型可能与其他数据关联，例如 BPMN 2.0 的流程定义。在删除模型时，还可能需要删除与该模型相关的流程定义记录。以下是一些可能的 SQL 语句：
        删除模型对应的流程定义:
        DELETE FROM ACT_RE_PROCDEF WHERE ID_ = :processDefinitionId
        注意：流程定义的删除通常不直接由模型的删除操作触发，除非模型被用于定义流程。如果模型没有被部署或与流程定义关联，这一步可能不会发生。

        3. 删除模型的版本
        Flowable 可能会记录模型的版本信息，通常存储在 ACT_RE_MODEL_REV 表中。删除模型时，相关的版本记录也需要被删除：
        DELETE FROM ACT_RE_MODEL_REV WHERE MODEL_ID_ = :modelId

        4. 删除模型的扩展信息
        如果模型包含扩展信息或其他关联表的数据，可能还需要从相应的表中删除相关记录。例如：
        DELETE FROM ACT_GE_BYTEARRAY WHERE ID_ IN (
            SELECT ID_ FROM ACT_RE_MODEL WHERE ID_ = :modelId
        )
         */
        repositoryService.deleteModel(modelId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deploy(String modelId) {
        // 获取流程模型：SELECT * FROM ACT_RE_MODEL WHERE ID_ = ?
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtil.isNull(model)) {
            throw new BusinessException("流程模型不存在！");
        }
        // 获取流程图：
        // SELECT EDITOR_SOURCE_ID_ FROM ACT_RE_MODEL WHERE ID_ = ?
        // SELECT BYTES_ FROM ACT_GE_BYTEARRAY WHERE ID_ = #{EDITOR_SOURCE_ID_}
        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelId);
        if (ArrayUtil.isEmpty(bpmnBytes)) {
            throw new BusinessException("请先设计流程图！");
        }
        String bpmnXml = StringUtils.toEncodedString(bpmnBytes, StandardCharsets.UTF_8);
        BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnXml);
        String processName = model.getName() + ".bpmn";
        // 部署流程：添加记录 ACT_RE_DEPLOYMENT
        Deployment deployment = repositoryService.createDeployment()
                .name(model.getName())
                .key(model.getKey())
                .category(model.getCategory())
                .addBytes(processName, bpmnBytes)
                .deploy();
        // 插入流程定义 ACT_RE_PROCDEF
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deployment.getId())
                .singleResult();
        // 修改流程定义的分类，便于搜索流程 ACT_RE_PROCDEF
        repositoryService.setProcessDefinitionCategory(procDef.getId(), model.getCategory());
        // 添加流程实例和表单关联关系
        wfDeployFormService.add(deployment.getId(), bpmnModel);

        /*
        模型表 (ACT_RE_MODEL) 中的 deployment_id 字段不会直接更新。模型的元数据和部署信息通常分开存储。
        ACT_GE_BYTEARRAY具体取哪条数据从ACT_RE_PROCDEF取对应的DEPLOYMENT_ID_，再去查ACT_GE_BYTEARRAY
         */
    }

    @Override
    public String queryBpmnImage(String modelId) {
        //根据modelId或者BpmnModel
        Model modelData = repositoryService.getModel(modelId);
        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelData.getId());
        if(bpmnBytes == null){
            throw new BusinessException("请先设计流程图！");
        }
        String bpmnXml = StringUtils.toEncodedString(bpmnBytes, StandardCharsets.UTF_8);
        BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnXml);
        //获得图片流
        byte[] imageBytes = BpmnModelUtils.generateBpmnImage(bpmnModel);
        // 将图像字节转换为 Base64 编码字符串
        return Base64.getEncoder().encodeToString(imageBytes);
    }

    @Override
    public void update(WfModelUpdateParam updateParam) {
        // 查询模型信息
        Model model = repositoryService.getModel(updateParam.getId());
        if (ObjectUtil.isNull(model)) {
            throw new BusinessException("模型不存在！");
        }
        model.setCategory(updateParam.getCategory());
        // 把备注添加到metaInfo中，后续在选择流程表单的时候需要把表单ID也设置到wfMetaInfo中
        if (StringUtils.isNotBlank(updateParam.getRemark())) {
            WfMetaInfo wfMetaInfo = new WfMetaInfo();
            wfMetaInfo.setRemark(updateParam.getRemark());
            model.setMetaInfo(JsonUtils.toJsonString(wfMetaInfo));
        }
        // 保存流程模型
        repositoryService.saveModel(model);
    }

    @Override
    public String queryBpmnXml(String modelId) {
        //根据modelId或者BpmnModel
        Model modelData = repositoryService.getModel(modelId);
        byte[] bpmnBytes = repositoryService.getModelEditorSource(modelData.getId());
        if(bpmnBytes == null){
            return "";
        }
        return StringUtils.toEncodedString(bpmnBytes, StandardCharsets.UTF_8);
    }
}
